using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Resources;
using System.Reflection;
using System.Threading;
using System.Diagnostics;
using System.Configuration;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.TransportProxy.Interop;
using Microsoft.XLANGs.BaseTypes;

namespace Microsoft.Samples.BizTalk.Adapter.Tcp.Receive
{
    /// <summary>
    ///     This class implements a receive adapter in BizTalk.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public partial class ReceiveAdapter : IBaseComponent,
                                          IBTTransport,
                                          IPersistPropertyBag,
                                          IBTTransportControl,
                                          IBTTransportConfig,
                                          IAdapter
    {
        #region Public Static Members

        public static readonly string BizTalkManagementDatabaseConnectionString = ConfigurationManager.ConnectionStrings["BizTalkManagementDatabase"].ConnectionString;

        #endregion

        #region Private Instance Members

        private Guid                                _clsid;                 // The class ID of this adapter
        private string                              _transportType;         // The transport type of this adapter
        private IBTTransportProxy                   _proxy;                 // Our transport proxy used to interact with messaging engine
        private int                                 _receiveBatchSize;      // Determines the number of messages submitted to BizTalk per batch
        private int                                 _receiveBatchTimeout;   // Specifies the number of milliseconds that must elapse before a batch is submitted
        private int                                 _receiveBatchMaxData;   // Specifies the maximum batch size that is submitted to BizTalk (if a single message exceeds this it will be submitted in its own batch)
        private int                                 _maxBatchWorkerThreads; // The number of batch worker threads
        private int                                 _maxDuplexWorkerThreads;// The number of duplex transmit worker threads
        private int                                 _maxTransmitWorkerThreads;// The number of transmit worker threads
        private int                                 _batchRetryCount;       // Batch retry count
        private int                                 _batchRetryInterval;    // Batch retry interval in seconds
        private Dictionary<string, ReceiveLocation> _endpoints;             // An array of active endpoints
        private bool                                _useBlockingSockets;    // Specifies whether there is a thread per receive location and blocking sockets or it uses the .NET thread pool
        private object                              _endpointLock;          // A lock object for the endpoint collection
        private TraceSwitch                         _receiveSwitch;         // Trace switch for receive adapter
        private TraceSwitch                         _socketSwitch;          // Trace switch for socket work
        private Dispatcher                          _dispatcher;            // The work dispatcher
        private string                              _deadMessageFolder;     // The location where dead messages are saved
        private bool                                _lazyInitDispatcher;    // Determines whether dispatcher should be lazily initialized
        private int                                 _threadIdleTimeout;     // Determines the timeout in minutes that a thread must be idle before being shut down

        #endregion

        #region Constructor

        /// <summary>
        ///     This is the default constructor for the class.
        /// </summary>
        public ReceiveAdapter()
        {
            // Initialize members
            this._clsid = new Guid("5AC11A4A-A961-4e92-98B9-D8F3E558303F");
            this._transportType = "TCP";
            this._proxy = null;
            this._receiveBatchSize = 10;
            this._receiveBatchTimeout = 250;
            this._receiveBatchMaxData = 1024;
            this._maxBatchWorkerThreads = 10;
            this._maxDuplexWorkerThreads = 5;
            this._maxTransmitWorkerThreads = 5;
            this._batchRetryCount = 5;
            this._batchRetryInterval = 10;
            this._useBlockingSockets = true;
            this._endpoints = new Dictionary<string, ReceiveLocation>();
            this._endpointLock = new object();
            this._receiveSwitch = new TraceSwitch("ReceiveAdapter", TraceHelper.ResourceManager.GetString(ResourceConstants.TraceSwitchReceiveAdapterDescription), "3");
            this._socketSwitch = new TraceSwitch("Socket", TraceHelper.ResourceManager.GetString(ResourceConstants.TraceSwitchSocketDescription), "0");
            this._dispatcher = null;
            this._deadMessageFolder = string.Empty;
            this._lazyInitDispatcher = true;
            this._threadIdleTimeout = 5;
        }

        #endregion

        #region Public Instance Properties

        /// <summary>
        ///     This property returns the maximum size of the receive batch that represents that maximum number of
        ///     messages to submit per batch.
        /// </summary>
        public int BatchSize
        {
            get { return (this._receiveBatchSize); }
        }

        /// <summary>
        ///     This property returns the maximum timeout in milliseconds that represents that amount of time that must
        ///     pass before a batch is submitted regardless of whether it is full or not.
        /// </summary>
        public int BatchTimeout
        {
            get { return (this._receiveBatchTimeout); }
        }

        /// <summary>
        ///     This property returns the maximum KB in a batch that represents the maximum size of the data added to a batch
        ///     before it is submitted regardless of whether the batch is full or not.
        /// </summary>
        public int MaxBatchData
        {
            get { return (this._receiveBatchMaxData); }
        }

        /// <summary>
        ///     This property returns the maximum number of worker threads that are used to process batches.
        /// </summary>
        public int MaxBatchWorkerThreads
        {
            get { return (this._maxBatchWorkerThreads); }
        }

        /// <summary>
        ///     This property returns the maximum number of worker threads that are used to send messages to duplex initiators.
        /// </summary>
        public int MaxDuplexWorkerThreads
        {
            get { return (this._maxDuplexWorkerThreads); }
        }

        /// <summary>
        ///     This property returns the maximum number of worker threads that are used to send responses to clients.
        /// </summary>
        public int MaxTransmitWorkerThreads
        {
            get { return (this._maxTransmitWorkerThreads); }
        }

        /// <summary>
        ///     This property returns whether the receive location should use a dedicated thread with blocking sockets for listening or
        ///     to use the .NET asynchronous thread pool based model.
        /// </summary>
        public bool UseBlockingSockets
        {
            get { return (this._useBlockingSockets); }
        }

        /// <summary>
        ///     This property returns the retry count that the adapter uses to retry failed batch submissions.
        /// </summary>
        public int BatchRetryCount
        {
            get { return (this._batchRetryCount); }
        }

        /// <summary>
        ///     This property returns the interval in seconds that the adapter waits between retries.
        /// </summary>
        public int BatchRetryInterval
        {
            get { return (this._batchRetryInterval); }
        }

        /// <summary>
        ///     This property determines whether the dispatcher should be lazily initialized.
        /// </summary>
        public bool LazyInitializeDispatcher
        {
            get { return (this._lazyInitDispatcher); }
        }

        /// <summary>
        ///     This property returns the number of minutes that a thread must be idle before being shut down.
        /// </summary>
        public int ThreadIdleTimeout
        {
            get { return (this._threadIdleTimeout); }
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method re-initializes the adapter state.
        /// </summary>
        private void ReInitialize()
        {
            // Clear down all endpoints
            this._endpoints = new Dictionary<string, ReceiveLocation>();

            // Re-create the dispatcher
            this._dispatcher = new Dispatcher(this, this.ThreadIdleTimeout);
        }

        /// <summary>
        ///     This method loads the handler configuration.
        /// </summary>
        private void LoadReceiveHandlerConfiguration(IPropertyBag configuration)
        {
            // Get XML document containing adapter configuration
            XmlDocument handlerConfiguration = ConfigurationHelper.LoadAdapterConfiguration(this, configuration);
            if (handlerConfiguration == null)
            {
                // Let it default
                return;
            }

            // Receive Batch Size
            XmlNode node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/receiveBatchSize");
            this._receiveBatchSize = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveBatchSize, this.BatchSize);

            // Batch Submission Timeout
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/receiveBatchTimeout");
            this._receiveBatchTimeout = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveBatchSubmissionTimeout, this.BatchTimeout);

            // Max Batch KB Size
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/receiveBatchMaxDataSize");
            this._receiveBatchMaxData = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveMaxBatchKbSize, this.MaxBatchData);

            // Batch Retry Count
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/batchRetryCount");
            this._batchRetryCount = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveBatchRetryCount, this._batchRetryCount);

            // Batch Retry Interval
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/batchRetryInterval");
            this._batchRetryInterval = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveBatchRetryInterval, this._batchRetryInterval);

            // Receive Batch Worker Threads
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/receiveBatchWorkerThreads");
            this._maxBatchWorkerThreads = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveBatchWorkerThreads, this.MaxBatchWorkerThreads);

            // Receive Duplex Worker Threads
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/receiveDuplexWorkerThreads");
            this._maxDuplexWorkerThreads = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveDuplexWorkerThreads, this.MaxDuplexWorkerThreads);

            // Receive Transmit Worker Threads
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/receiveTransmitWorkerThreads");
            this._maxTransmitWorkerThreads = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveTransmitWorkerThreads, this.MaxTransmitWorkerThreads);

            // Use Blocking Sockets
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/receiveUseBlockingSockets");
            this._useBlockingSockets = bool.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveUseBlockingSockets, this.UseBlockingSockets);

            // Dead Message Folder
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/deadMessageFolder");
            this._deadMessageFolder = node.InnerText;
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveDeadMessageFolder, this.DeadMessageFolder);

            // Lazy Initialize Dispatcher
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/threadLazyInitialization");
            this._lazyInitDispatcher = bool.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveLazyInitializeDispatcher, this.LazyInitializeDispatcher);

            // Thread Idle Timeout
            node = ConfigurationHelper.GetEntry(handlerConfiguration, "/Config/threadIdleTimeout");
            this._threadIdleTimeout = int.Parse(node.InnerText);
            if (this.TraceSwitch.TraceVerbose) TraceHelper.WriteEntry(TraceLevel.Verbose, ResourceConstants.ReceiveThreadIdleTimeout, this.ThreadIdleTimeout);
        }

        /// <summary>
        ///     This method creates a new receive location from the configuration.
        /// </summary>
        private ReceiveLocation CreateReceiveLocation(string url, IPropertyBag adapterConfiguration, IPropertyBag bizTalkConfiguration)
        {
            // Get XML document containing adapter configuration
            XmlDocument locationConfiguration = ConfigurationHelper.LoadAdapterConfiguration(this, adapterConfiguration);
            if (locationConfiguration == null)
            {
                string message = TraceHelper.FormatEntry(ResourceConstants.ReceiveLocationConfigurationNotExist, url);
                TraceHelper.WriteRawEntry(TraceLevel.Error, message);
                throw new AdapterConfigurationException(message);
            }

            // Create a new receive location and return it
            return (new ReceiveLocation(this, url, locationConfiguration, bizTalkConfiguration));
        }

        #endregion
    }
}
